UNPKG

5.32 kBJavaScriptView Raw
1'use strict';
2
3const Mixed = require('../schema/mixed');
4const deepEqual = require('../utils').deepEqual;
5const get = require('../helpers/get');
6const handleSpreadDoc = require('../helpers/document/handleSpreadDoc');
7const util = require('util');
8const specialProperties = require('../helpers/specialProperties');
9
10const populateModelSymbol = require('../helpers/symbols').populateModelSymbol;
11
12/*!
13 * ignore
14 */
15
16class MongooseMap extends Map {
17 constructor(v, path, doc, schemaType) {
18 if (v != null && v.constructor.name === 'Object') {
19 v = Object.keys(v).reduce((arr, key) => arr.concat([[key, v[key]]]), []);
20 }
21 super(v);
22
23 this.$__parent = doc != null && doc.$__ != null ? doc : null;
24 this.$__path = path;
25 this.$__schemaType = schemaType == null ? new Mixed(path) : schemaType;
26
27 this.$__runDeferred();
28 }
29
30 $init(key, value) {
31 checkValidKey(key);
32
33 super.set(key, value);
34
35 if (value != null && value.$isSingleNested) {
36 value.$basePath = this.$__path + '.' + key;
37 }
38 }
39
40 $__set(key, value) {
41 super.set(key, value);
42 }
43
44 get(key, options) {
45 options = options || {};
46 if (options.getters === false) {
47 return super.get(key);
48 }
49 return this.$__schemaType.applyGetters(super.get(key), this.$__parent);
50 }
51
52 set(key, value) {
53 checkValidKey(key);
54 value = handleSpreadDoc(value);
55
56 // Weird, but because you can't assign to `this` before calling `super()`
57 // you can't get access to `$__schemaType` to cast in the initial call to
58 // `set()` from the `super()` constructor.
59
60 if (this.$__schemaType == null) {
61 this.$__deferred = this.$__deferred || [];
62 this.$__deferred.push({ key: key, value: value });
63 return;
64 }
65
66 const fullPath = this.$__path + '.' + key;
67 const populated = this.$__parent != null && this.$__parent.$__ ?
68 this.$__parent.populated(fullPath) || this.$__parent.populated(this.$__path) :
69 null;
70 const priorVal = this.get(key);
71
72 if (populated != null) {
73 if (value.$__ == null) {
74 value = new populated.options[populateModelSymbol](value);
75 }
76 value.$__.wasPopulated = true;
77 } else {
78 try {
79 value = this.$__schemaType.
80 applySetters(value, this.$__parent, false, this.get(key));
81 } catch (error) {
82 if (this.$__parent != null && this.$__parent.$__ != null) {
83 this.$__parent.invalidate(fullPath, error);
84 return;
85 }
86 throw error;
87 }
88 }
89
90 super.set(key, value);
91
92 if (value != null && value.$isSingleNested) {
93 value.$basePath = this.$__path + '.' + key;
94 }
95
96 const parent = this.$__parent;
97 if (parent != null && parent.$__ != null && !deepEqual(value, priorVal)) {
98 parent.markModified(this.$__path + '.' + key);
99 }
100 }
101
102 delete(key) {
103 this.set(key, undefined);
104 super.delete(key);
105 }
106
107 toBSON() {
108 return new Map(this);
109 }
110
111 toObject(options) {
112 if (get(options, 'flattenMaps')) {
113 const ret = {};
114 const keys = this.keys();
115 for (const key of keys) {
116 ret[key] = this.get(key);
117 }
118 return ret;
119 }
120
121 return new Map(this);
122 }
123
124 toJSON() {
125 const ret = {};
126 const keys = this.keys();
127 for (const key of keys) {
128 ret[key] = this.get(key);
129 }
130 return ret;
131 }
132
133 inspect() {
134 return new Map(this);
135 }
136
137 $__runDeferred() {
138 if (!this.$__deferred) {
139 return;
140 }
141
142 for (const keyValueObject of this.$__deferred) {
143 this.set(keyValueObject.key, keyValueObject.value);
144 }
145
146 this.$__deferred = null;
147 }
148}
149
150if (util.inspect.custom) {
151 Object.defineProperty(MongooseMap.prototype, util.inspect.custom, {
152 enumerable: false,
153 writable: false,
154 configurable: false,
155 value: MongooseMap.prototype.inspect
156 });
157}
158
159Object.defineProperty(MongooseMap.prototype, '$__set', {
160 enumerable: false,
161 writable: true,
162 configurable: false
163});
164
165Object.defineProperty(MongooseMap.prototype, '$__parent', {
166 enumerable: false,
167 writable: true,
168 configurable: false
169});
170
171Object.defineProperty(MongooseMap.prototype, '$__path', {
172 enumerable: false,
173 writable: true,
174 configurable: false
175});
176
177Object.defineProperty(MongooseMap.prototype, '$__schemaType', {
178 enumerable: false,
179 writable: true,
180 configurable: false
181});
182
183Object.defineProperty(MongooseMap.prototype, '$isMongooseMap', {
184 enumerable: false,
185 writable: false,
186 configurable: false,
187 value: true
188});
189
190Object.defineProperty(MongooseMap.prototype, '$__deferredCalls', {
191 enumerable: false,
192 writable: false,
193 configurable: false,
194 value: true
195});
196
197/*!
198 * Since maps are stored as objects under the hood, keys must be strings
199 * and can't contain any invalid characters
200 */
201
202function checkValidKey(key) {
203 const keyType = typeof key;
204 if (keyType !== 'string') {
205 throw new TypeError(`Mongoose maps only support string keys, got ${keyType}`);
206 }
207 if (key.startsWith('$')) {
208 throw new Error(`Mongoose maps do not support keys that start with "$", got "${key}"`);
209 }
210 if (key.includes('.')) {
211 throw new Error(`Mongoose maps do not support keys that contain ".", got "${key}"`);
212 }
213 if (specialProperties.has(key)) {
214 throw new Error(`Mongoose maps do not support reserved key name "${key}"`);
215 }
216}
217
218module.exports = MongooseMap;