@pbvision/firestore-orm 0.0.2

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.
@@ -0,0 +1,75 @@
1
+ const S = require('@pbvision/schema')
2
+
3
+ const {
4
+ getWithArgs,
5
+ Context
6
+ } = require('./context')
7
+ const {
8
+ DeletedTwiceError,
9
+ InvalidFieldError,
10
+ InvalidOptionsError,
11
+ InvalidParameterError,
12
+ ModelAlreadyExistsError,
13
+ ModelTrackedTwiceError,
14
+ TransactionFailedError,
15
+ WriteAttemptedInReadOnlyTxError
16
+ } = require('./errors')
17
+ const {
18
+ __Field,
19
+ ArrayField,
20
+ BooleanField,
21
+ NumberField,
22
+ ObjectField,
23
+ StringField
24
+ } = require('./fields')
25
+ const { Key, UniqueKeyList } = require('./key')
26
+ const { Model } = require('./models')
27
+
28
+ /**
29
+ * @module firestore
30
+ */
31
+
32
+ /**
33
+ * Setup the Firestore library before returning symbols clients can use.
34
+ *
35
+ * @param {Object} [firestoreDB] client to interact with db docs; from
36
+ * firebase/app::initializeApp
37
+ * @returns {Object} Symbols that clients of this library can use.
38
+ * @private
39
+ */
40
+ function setup (firestoreDB) {
41
+ Key.firestoreDB = firestoreDB
42
+ const toExport = {
43
+ S,
44
+ Model,
45
+ UniqueKeyList,
46
+ Context,
47
+ firestoreDB,
48
+
49
+ // Errors
50
+ DeletedTwiceError,
51
+ InvalidFieldError,
52
+ InvalidOptionsError,
53
+ InvalidParameterError,
54
+ ModelTrackedTwiceError,
55
+ ModelAlreadyExistsError,
56
+ TransactionFailedError,
57
+ WriteAttemptedInReadOnlyTxError
58
+ }
59
+ if (Number(process.env.INDEBUGGER)) {
60
+ toExport.__private = {
61
+ __Field,
62
+ fields: [
63
+ ArrayField,
64
+ BooleanField,
65
+ NumberField,
66
+ ObjectField,
67
+ StringField
68
+ ],
69
+ getWithArgs
70
+ }
71
+ }
72
+ return toExport
73
+ }
74
+
75
+ module.exports = setup
package/src/key.js ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Key uniquely identifies a model.
3
+ */
4
+ class Key {
5
+ /**
6
+ * @param {Model} Cls a Model class
7
+ * @param {Object} encodedKey encoded key
8
+ * @param {Object} keyComponents key component values
9
+ * @private
10
+ */
11
+ constructor (Cls, encodedKey, keyComponents) {
12
+ this.Cls = Cls
13
+ this.encodedKey = encodedKey
14
+ this.keyComponents = keyComponents
15
+ }
16
+
17
+ get docRef () {
18
+ const db = Key.firestoreDB
19
+ return db.collection(this.Cls.collectionName).doc(this.encodedKey)
20
+ }
21
+ }
22
+
23
+ /**
24
+ * An array which ensures it has no more than one copy of any key.
25
+ *
26
+ * This is useful because key equality cannot be checked with the
27
+ * built-in equality operator.
28
+ */
29
+ class UniqueKeyList extends Array {
30
+ constructor (...keys) {
31
+ super(...keys)
32
+ const hashes = keys.map(key => this.constructor.getKeyHash(key))
33
+ this.__keyHashes = new Set(hashes)
34
+ }
35
+
36
+ static getKeyHash (key) {
37
+ return `${key.Cls.name}::${key.encodedKey}`
38
+ }
39
+
40
+ push (...keys) {
41
+ for (const key of keys) {
42
+ const keyHash = this.constructor.getKeyHash(key)
43
+ if (!this.__keyHashes.has(keyHash)) {
44
+ this.__keyHashes.add(keyHash)
45
+ super.push(key)
46
+ }
47
+ }
48
+ }
49
+
50
+ filter (...args) {
51
+ return Array.prototype.filter.bind(this, ...args)
52
+ }
53
+
54
+ map (f) {
55
+ const ret = []
56
+ for (let i = 0; i < this.length; i++) {
57
+ ret.push(f(this[i]))
58
+ }
59
+ return ret
60
+ }
61
+ }
62
+
63
+ module.exports = {
64
+ Key,
65
+ UniqueKeyList
66
+ }
package/src/models.js ADDED
@@ -0,0 +1,542 @@
1
+ const assert = require('assert')
2
+
3
+ const { Transaction } = require('@google-cloud/firestore')
4
+ const S = require('@pbvision/schema')
5
+ const stableStringify = require('fast-json-stable-stringify')
6
+
7
+ const { Data } = require('./data')
8
+ const {
9
+ InvalidFieldError,
10
+ InvalidParameterError,
11
+ GenericModelError
12
+ } = require('./errors')
13
+ const { __Field, SCHEMA_TYPE_TO_FIELD_CLASS_MAP } = require('./fields')
14
+ const { Key } = require('./key')
15
+ const {
16
+ validateValue,
17
+ SCHEMA_TYPE_TO_JS_TYPE_MAP
18
+ } = require('./utils')
19
+
20
+ /**
21
+ * The base class for modeling data.
22
+ */
23
+ class Model {
24
+ /**
25
+ * Create a representation of a database Doc. Should only be used by the
26
+ * library.
27
+ */
28
+ constructor (isNew, vals, isForUpdateAndMayBePartial = false, isSet = false) {
29
+ this.constructor.__doOneTimeModelPrep()
30
+ assert.ok(typeof isNew === 'boolean', 'isNew must be a boolean')
31
+ assert.ok(typeof isForUpdateAndMayBePartial === 'boolean',
32
+ 'isForUpdateAndMayBePartial must be a boolean')
33
+ this.isNew = isNew
34
+ this.__isPartial = isForUpdateAndMayBePartial
35
+ this.__isSet = isSet
36
+ assert.ok(!isSet || !isForUpdateAndMayBePartial,
37
+ 'may not be partial when using isSet')
38
+
39
+ // __cached_attrs has a __Field subclass object for each non-key attribute.
40
+ this.__cached_attrs = {}
41
+
42
+ // __cached_attrs has a __Field subclass object for each non-key attribute.
43
+ this.__attr_getters = {}
44
+
45
+ // pull out the Key for this doc
46
+ let keyComponents
47
+ if (vals.__id !== undefined) {
48
+ keyComponents = this.constructor.__decodeCompoundValue(
49
+ this.constructor.__keyOrder, vals.__id)
50
+ delete vals._id
51
+ Object.assign(vals, keyComponents)
52
+ } else {
53
+ this.__key = this.constructor.key(vals, true)
54
+ }
55
+
56
+ // add user-defined fields from FIELDS & key components from KEY
57
+ for (const [name, opts] of Object.entries(this.constructor._attrs)) {
58
+ this.__addField(name, opts, vals)
59
+ }
60
+
61
+ Object.seal(this)
62
+ }
63
+
64
+ static async register (registrar) {
65
+ this.__doOneTimeModelPrep()
66
+ await registrar.registerModel(this)
67
+ }
68
+
69
+ /**
70
+ * Hook for finalizing a model before writing to database
71
+ */
72
+ async finalize () {
73
+ }
74
+
75
+ __addField (name, opts, vals) {
76
+ const valSpecified = Object.hasOwnProperty.call(vals, name)
77
+ const getCachedField = () => {
78
+ if (this.__cached_attrs[name]) {
79
+ return this.__cached_attrs[name]
80
+ }
81
+ const Cls = SCHEMA_TYPE_TO_FIELD_CLASS_MAP[opts.schema.type]
82
+ // can't force validation of undefined values for blind updates because
83
+ // they are permitted to omit fields
84
+ const field = new Cls({
85
+ name,
86
+ opts,
87
+ val: vals[name],
88
+ valIsFromDB: !this.isNew && !this.__isPartial,
89
+ valSpecified: valSpecified,
90
+ isForUpdate: this.__isPartial
91
+ })
92
+ Object.seal(field)
93
+ this.__cached_attrs[name] = field
94
+ return field
95
+ }
96
+ this.__attr_getters[name] = getCachedField
97
+ if (this.isNew || (this.__isPartial && valSpecified)) {
98
+ getCachedField() // create the field now to trigger validation
99
+ }
100
+ Object.defineProperty(this, name, {
101
+ get: () => {
102
+ const field = getCachedField()
103
+ return field.get()
104
+ },
105
+ set: (val) => {
106
+ const field = getCachedField()
107
+ field.set(val)
108
+ }
109
+ })
110
+ }
111
+
112
+ static __getFields () {
113
+ return this.FIELDS
114
+ }
115
+
116
+ static __validatedSchema () {
117
+ if (Object.constructor.hasOwnProperty.call(this, '__CACHED_SCHEMA')) {
118
+ return this.__CACHED_SCHEMA
119
+ }
120
+
121
+ if (!this.KEY) {
122
+ throw new InvalidFieldError('KEY', 'the partition key is required')
123
+ }
124
+ if (this.KEY.isSchema || this.KEY.schema) {
125
+ throw new InvalidFieldError('KEY', 'must define key component name(s)')
126
+ }
127
+ if (Object.keys(this.KEY).length === 0) {
128
+ throw new InvalidFieldError('KEY', '/at least one partition key field/')
129
+ }
130
+
131
+ // cannot use the names of non-static Model members (only need to list
132
+ // those that are defined by the constructor; those which are on the
133
+ // prototype are enforced automatically)
134
+ const reservedNames = new Set(['isNew'])
135
+ const proto = this.prototype
136
+ const ret = {}
137
+ for (const schema of [this.KEY, this.__getFields()]) {
138
+ for (const [key, val] of Object.entries(schema)) {
139
+ if (ret[key]) {
140
+ throw new InvalidFieldError(
141
+ key, 'property name cannot be used more than once')
142
+ }
143
+ if (reservedNames.has(key)) {
144
+ throw new InvalidFieldError(
145
+ key, 'field name is reserved and may not be used')
146
+ }
147
+ if (key in proto) {
148
+ throw new InvalidFieldError(key, 'shadows a property name')
149
+ }
150
+ ret[key] = val
151
+ }
152
+ }
153
+ this.__CACHED_SCHEMA = S.obj(ret)
154
+ return this.__CACHED_SCHEMA
155
+ }
156
+
157
+ static get schema () {
158
+ return this.__validatedSchema()
159
+ }
160
+
161
+ static get __keyOrder () {
162
+ if (Object.constructor.hasOwnProperty.call(this, '__CACHED_KEY_ORDER')) {
163
+ return this.__CACHED_KEY_ORDER
164
+ }
165
+ this.__validatedSchema() // use side effect to validate schema
166
+ this.__CACHED_KEY_ORDER = Object.keys(this.KEY).sort()
167
+ return this.__CACHED_KEY_ORDER
168
+ }
169
+
170
+ static __validateCollectionName () {
171
+ const collectionName = this.collectionName
172
+ try {
173
+ assert.ok(!collectionName.endsWith('Model'), 'not include "Model"')
174
+ assert.ok(!collectionName.endsWith('Table'), 'not include "Table"')
175
+ assert.ok(!collectionName.endsWith('Collection'), 'not include "Collection"')
176
+ assert.ok(collectionName.indexOf('_') < 0, 'not include underscores')
177
+ assert.ok(collectionName[0].match(/[A-Z]/), 'start with a capitalized letter')
178
+ assert.ok(collectionName.match(/[a-zA-Z0-9]*/), 'only use letters or numbers')
179
+ } catch (e) {
180
+ throw new Error(`Bad collection name "${collectionName}": it must ${e.message}`)
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Check that field names don't overlap, etc.
186
+ * @package
187
+ */
188
+ static __doOneTimeModelPrep () {
189
+ // need to check hasOwnProperty because we don't want to access this
190
+ // property via inheritance (i.e., our parent may have been setup, but
191
+ // the subclass must do its own setup)
192
+ if (Object.hasOwnProperty.call(this, '__setupDone')) {
193
+ return // one-time setup already done
194
+ }
195
+ this.__setupDone = true
196
+
197
+ this.__validateCollectionName()
198
+ // _attrs maps the name of attributes that are visible to users of
199
+ // this model. This is the combination of attributes (keys) defined by KEY
200
+ // and FIELDS.
201
+ this._attrs = {}
202
+ this.__KEY_COMPONENT_NAMES = new Set()
203
+ const partitionKeys = new Set(this.__keyOrder)
204
+ for (const [fieldName, schema] of Object.entries(this.schema.objectSchemas)) {
205
+ const isKey = partitionKeys.has(fieldName)
206
+ const finalFieldOpts = __Field.__validateFieldOptions(
207
+ this.collectionName, isKey, fieldName, schema)
208
+ this._attrs[fieldName] = finalFieldOpts
209
+ if (isKey) {
210
+ this.__KEY_COMPONENT_NAMES.add(fieldName)
211
+ }
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Defines the key. Every doc in the database is uniquely identified by its'
217
+ * key. The default key is a UUIDv4.
218
+ *
219
+ * A key can simply be some scalar value:
220
+ * static KEY = { id: S.str }
221
+ *
222
+ * A key may can be "compound key", i.e., a key with one or components, each
223
+ * with their own name and schema:
224
+ * static KEY = {
225
+ * email: S.str,
226
+ * birthYear: S.int.min(1900)
227
+ * }
228
+ */
229
+ static KEY = { id: S.SCHEMAS.UUID }
230
+
231
+ /**
232
+ * Defines the non-key fields. By default there are no fields.
233
+ *
234
+ * Properties are defined as a map from field names to Schema objects:
235
+ * @example
236
+ * static FIELDS = {
237
+ * someNumber: S.double,
238
+ * someNumberWithOptions: S.double.optional().default(0).readOnly()
239
+ * }
240
+ */
241
+ static FIELDS = {}
242
+
243
+ get _id () {
244
+ return this.constructor.__encodeCompoundValue(
245
+ this.constructor.__keyOrder,
246
+ new Proxy(this, {
247
+ get: (target, prop, receiver) => {
248
+ return target.getField(prop).__value
249
+ }
250
+ })
251
+ )
252
+ }
253
+
254
+ /**
255
+ * Returns the underlying __Field associated with an attribute.
256
+ *
257
+ * @param {String} name the name of a field from FIELDS
258
+ * @returns {BooleanField|ArrayField|ObjectField|NumberField|StringField}
259
+ */
260
+ getField (name) {
261
+ assert(!name.startsWith('_'), 'may not access internal computed fields')
262
+ return this.__attr_getters[name]()
263
+ }
264
+
265
+ /**
266
+ * This is the name of the collection this model is for. By default, the
267
+ * collection name is the model's class name. However, classes may choose
268
+ * to override this method and provide there own name (e.g., for co-existed
269
+ * models where multiple models have data in one collection).
270
+ *
271
+ * @type {String}
272
+ */
273
+ static get collectionName () {
274
+ return this.name
275
+ }
276
+
277
+ /**
278
+ * Given a mapping, split compositeKeys from other model fields. Return a
279
+ * 3-tuple, [encodedKey, keyComponents, modelData].
280
+ *
281
+ * @param {Object} data data to be split
282
+ * @package
283
+ */
284
+ static __splitKeysAndData (data) {
285
+ const keyComponents = {}
286
+ const modelData = {}
287
+ Object.keys(data).forEach(key => {
288
+ if (this.__KEY_COMPONENT_NAMES.has(key)) {
289
+ keyComponents[key] = data[key]
290
+ } else if (this._attrs[key]) {
291
+ modelData[key] = data[key]
292
+ } else {
293
+ throw new InvalidParameterError('data', 'unknown field ' + key)
294
+ }
295
+ })
296
+ const _id = this.__encodeCompoundValue(this.__keyOrder, keyComponents)
297
+ return [_id, keyComponents, modelData]
298
+ }
299
+
300
+ async __write (ctx) {
301
+ // If ctx is a Transaction object, then its mutator methods like create(),
302
+ // etc.return the Transaction object (for chaining). There's no promise in
303
+ // this case because the updates will be flushed when the transaction
304
+ // commits.
305
+ // If ctx is the Firestore object, then a promise is returned. In that case
306
+ // we await on it here.
307
+ // The return value from this function is always undefined for consistency.
308
+ const ret = this.__writeHelper(ctx)
309
+ if (ret instanceof Transaction) {
310
+ return
311
+ }
312
+ await ret
313
+ }
314
+
315
+ __writeHelper (ctx) {
316
+ const docRef = this.__key.docRef
317
+ this.finalize()
318
+ const data = {}
319
+ for (const field of Object.values(this.__cached_attrs)) {
320
+ if (!field.isKey) {
321
+ if (field.hasChangesToCommit(true) || this.isNew) {
322
+ const val = field.__valueForFirestoreWrite()
323
+ if (val !== undefined) {
324
+ data[field.name] = val
325
+ }
326
+ }
327
+ }
328
+ }
329
+
330
+ if (this.isNew) {
331
+ // write the entire document from scratch
332
+ if (this.__isSet) {
333
+ // overwrite if it already exists (create if missing)
334
+ return ctx.__dbCtx.set(docRef, data, { merge: false })
335
+ } else {
336
+ // fail if it already exists
337
+ return ctx.__dbCtx.create(docRef, data)
338
+ }
339
+ } else {
340
+ if (!Object.keys(data).length) {
341
+ throw new GenericModelError(
342
+ 'update did not provide any data to change',
343
+ this.constructor.collectionName, this.__key.encodedKey)
344
+ }
345
+ return ctx.__dbCtx.update(docRef, data)
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Indicates if any field was mutated. New models are considered to be
351
+ * mutated as well.
352
+ * @param {Boolean} expectWrites whether the model will be updated,
353
+ * default is true.
354
+ * @type {Boolean}
355
+ * @package
356
+ */
357
+ __isMutated (expectWrites = true) {
358
+ if (this.isNew) {
359
+ return true
360
+ }
361
+ for (const field of Object.values(this.__cached_attrs)) {
362
+ if (field.hasChangesToCommit(expectWrites)) {
363
+ // If any field has changes that need to be committed,
364
+ // it will mark the model as mutated.
365
+ return true
366
+ }
367
+ }
368
+ return false
369
+ }
370
+
371
+ /**
372
+ * Returns the string representation for the given compound values.
373
+ *
374
+ * This method throws {@link InvalidFieldError} if the compound value does
375
+ * not match the required schema.
376
+ *
377
+ * @param {Array<String>} keyOrder order of keys in the string representation
378
+ * @param {Object} values maps component names to values; may have extra
379
+ * fields (they will be ignored)
380
+ * @package
381
+ */
382
+ static __encodeCompoundValue (keyOrder, values) {
383
+ const pieces = []
384
+ for (let i = 0; i < keyOrder.length; i++) {
385
+ const fieldName = keyOrder[i]
386
+ const fieldOpts = this._attrs[fieldName]
387
+ const givenValue = values[fieldName]
388
+ if (givenValue === undefined) {
389
+ throw new InvalidFieldError(fieldName, 'must be provided')
390
+ }
391
+ const valueType = validateValue(fieldName, fieldOpts, givenValue)
392
+ if (valueType === String) {
393
+ // the '\0' character cannot be stored in string fields. If you need to
394
+ // store a string containing this character, then you need to store it
395
+ // inside of an object field, e.g.,
396
+ // doc.someObjField = { myString: '\0' } is okay
397
+ if (givenValue.indexOf('\0') !== -1) {
398
+ throw new InvalidFieldError(
399
+ fieldName, 'cannot put null bytes in strings in compound values')
400
+ }
401
+ pieces.push(givenValue)
402
+ } else {
403
+ pieces.push(stableStringify(givenValue))
404
+ }
405
+ }
406
+ return pieces.join('\0')
407
+ }
408
+
409
+ /**
410
+ * Returns the map which corresponds to the given compound value string
411
+ *
412
+ * This method throws {@link InvalidFieldError} if the decoded string does
413
+ * not match the required schema.
414
+ *
415
+ * @param {Array<String>} keyOrder order of keys in the string representation
416
+ * @param {String} strVal the string representation of a compound value
417
+ * @package
418
+ */
419
+ static __decodeCompoundValue (keyOrder, val) {
420
+ // Assume val is otherwise a string
421
+ const pieces = val.split('\0')
422
+ if (pieces.length !== keyOrder.length) {
423
+ throw new InvalidFieldError(
424
+ 'KEY', 'failed to parse key: incorrect number of components')
425
+ }
426
+
427
+ const compoundID = {}
428
+ for (let i = 0; i < pieces.length; i++) {
429
+ const piece = pieces[i]
430
+ const fieldName = keyOrder[i]
431
+ const fieldOpts = this._attrs[fieldName]
432
+ const valueType = SCHEMA_TYPE_TO_JS_TYPE_MAP[fieldOpts.schema.type]
433
+ if (valueType === String) {
434
+ compoundID[fieldName] = piece
435
+ } else {
436
+ compoundID[fieldName] = JSON.parse(piece)
437
+ }
438
+
439
+ validateValue(fieldName, fieldOpts, compoundID[fieldName])
440
+ }
441
+ return compoundID
442
+ }
443
+
444
+ /**
445
+ * Returns a Key identifying a unique document in this model's DB collection.
446
+ * @param {*} vals map of key component names to values; if there is
447
+ * only one partition key field (whose type is not object), then this MAY
448
+ * instead be just that field's value.
449
+ * @returns {Key} a Key object.
450
+ */
451
+ static key (vals, ignoreData = false) {
452
+ const processedVals = this.__splitKeysAndDataWithPreprocessing(vals)
453
+ const [encodedKey, keyComponents, data] = processedVals
454
+
455
+ // ensure that vals only contained key components (no data components)
456
+ const dataKeys = Object.keys(data)
457
+ if (dataKeys.length && !ignoreData) {
458
+ dataKeys.sort()
459
+ throw new InvalidParameterError('vals',
460
+ `received non-key fields: ${dataKeys.join(', ')}`)
461
+ }
462
+ return new Key(this, encodedKey, keyComponents)
463
+ }
464
+
465
+ /**
466
+ * Returns a Data fully describing a unique document in this model's DB collection.
467
+ * @param {*} vals like the argument to key() but also includes non-key data
468
+ * @returns {Data} a Data object for use with tx.create() or
469
+ * tx.get(..., { createIfMissing: true })
470
+ */
471
+ static data (vals) {
472
+ return new Data(this, ...this.__splitKeysAndDataWithPreprocessing(vals))
473
+ }
474
+
475
+ /** @package */
476
+ static __splitKeysAndDataWithPreprocessing (vals) {
477
+ // if we only have one key component, then the `_id` **MAY** just be the
478
+ // value rather than a map of key component names to values
479
+ this.__doOneTimeModelPrep()
480
+ const pKeyOrder = this.__keyOrder
481
+ if (pKeyOrder.length === 1) {
482
+ const pFieldName = pKeyOrder[0]
483
+ if (!(vals instanceof Object) || !vals[pFieldName]) {
484
+ vals = { [pFieldName]: vals }
485
+ }
486
+ }
487
+ if (!(vals instanceof Object)) {
488
+ throw new InvalidParameterError('values',
489
+ 'should be an object mapping key component names to values')
490
+ }
491
+ return this.__splitKeysAndData(vals)
492
+ }
493
+
494
+ /**
495
+ * Returns the document path to this object.
496
+ */
497
+ toString () {
498
+ return this.__key.docRef.path
499
+ }
500
+
501
+ toJSON () {
502
+ return this.getSnapshot()
503
+ }
504
+
505
+ /**
506
+ * Return snapshot of the model, all fields included.
507
+ * @param {Object} params
508
+ * @param {Boolean} params.initial Whether to return the initial state
509
+ * @param {Boolean} params.dbKeys Whether to return _id instead of
510
+ * raw key fields.
511
+ * @param {Boolean} params.omitKey whether to omit the key
512
+ */
513
+ getSnapshot ({ initial = false, dbKeys = false, omitKey = false } = {}) {
514
+ const ret = {}
515
+ if (dbKeys && !omitKey) {
516
+ if (!initial || !this.isNew) {
517
+ assert.ok(typeof this._id === 'string')
518
+ ret._id = this._id
519
+ } else {
520
+ ret._id = undefined
521
+ }
522
+ }
523
+ for (const [name, getter] of Object.entries(this.__attr_getters)) {
524
+ const field = getter()
525
+ if (field.isKey) {
526
+ if (dbKeys || omitKey) {
527
+ continue
528
+ }
529
+ }
530
+ if (initial) {
531
+ ret[name] = field.__initialValue
532
+ } else {
533
+ ret[name] = field.__value
534
+ }
535
+ }
536
+ return ret
537
+ }
538
+ }
539
+
540
+ module.exports = {
541
+ Model
542
+ }