monastery 1.35.0 → 1.36.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.
package/test/crud.js CHANGED
@@ -145,7 +145,7 @@ module.exports = function(monastery, opendb) {
145
145
  db2.close()
146
146
  })
147
147
 
148
- test('update basics', async () => {
148
+ test('update general', async () => {
149
149
  let db = (await opendb(null)).db
150
150
  let user = db.model('user', {
151
151
  fields: {
@@ -335,7 +335,7 @@ module.exports = function(monastery, opendb) {
335
335
 
336
336
  test('find default field population', async () => {
337
337
  let db = (await opendb(null)).db
338
- let user = db.model('user', {
338
+ db.model('user', {
339
339
  fields: {
340
340
  name: { type: 'string', default: 'Martin Luther' },
341
341
  addresses: [{ city: { type: 'string' }, country: { type: 'string', default: 'Germany' } }],
@@ -344,7 +344,7 @@ module.exports = function(monastery, opendb) {
344
344
  dogs: [{ model: 'dog' }], // virtual association
345
345
  }
346
346
  })
347
- let dog = db.model('dog', {
347
+ db.model('dog', {
348
348
  fields: {
349
349
  name: { type: 'string', default: 'Scruff' },
350
350
  user: { model: 'user' }
@@ -352,35 +352,35 @@ module.exports = function(monastery, opendb) {
352
352
  })
353
353
 
354
354
  // Default field doesn't override null
355
- let nulldoc1 = await dog.insert({ data: { name: null }})
356
- let nullfind1 = await dog.findOne({ query: nulldoc1._id })
355
+ let nulldoc1 = await db.dog.insert({ data: { name: null }})
356
+ let nullfind1 = await db.dog.findOne({ query: nulldoc1._id })
357
357
  expect(nullfind1).toEqual({ _id: nulldoc1._id, name: null })
358
358
 
359
359
  // Default field doesn't override empty string
360
- let nulldoc2 = await dog.insert({ data: { name: '' }})
361
- let nullfind2 = await dog.findOne({ query: nulldoc2._id })
360
+ let nulldoc2 = await db.dog.insert({ data: { name: '' }})
361
+ let nullfind2 = await db.dog.findOne({ query: nulldoc2._id })
362
362
  expect(nullfind2).toEqual({ _id: nulldoc2._id, name: '' })
363
363
 
364
364
  // Default field overrides undefined
365
- let nulldoc3 = await dog.insert({ data: { name: undefined }})
366
- let nullfind3 = await dog.findOne({ query: nulldoc3._id })
365
+ let nulldoc3 = await db.dog.insert({ data: { name: undefined }})
366
+ let nullfind3 = await db.dog.findOne({ query: nulldoc3._id })
367
367
  expect(nullfind3).toEqual({ _id: nullfind3._id, name: 'Scruff' })
368
368
 
369
369
  // Default field population test
370
- // Note that addresses.1.country shouldn't be overriden
370
+ // Note that addresses.1.country shouldn't be overridden
371
371
  // Insert documents (without defaults)
372
- let inserted = await dog._insert({})
373
- let inserted2 = await user._insert({
372
+ let dog1 = await db.dog._insert({})
373
+ let user1 = await db.user._insert({
374
374
  addresses: [
375
375
  { city: 'Frankfurt' },
376
376
  { city: 'Christchurch', country: 'New Zealand' }
377
377
  ],
378
- pet: { dog: inserted._id }
378
+ pet: { dog: dog1._id }
379
379
  })
380
- await dog._update(inserted._id, { $set: { user: inserted2._id }})
380
+ await db.dog._update(dog1._id, { $set: { user: user1._id }})
381
381
 
382
- let find1 = await user.findOne({
383
- query: inserted2._id,
382
+ let find1 = await db.user.findOne({
383
+ query: user1._id,
384
384
  populate: ['pet.dog', {
385
385
  from: 'dog',
386
386
  localField: '_id',
@@ -389,20 +389,20 @@ module.exports = function(monastery, opendb) {
389
389
  }]
390
390
  })
391
391
  expect(find1).toEqual({
392
- _id: inserted2._id,
392
+ _id: user1._id,
393
393
  name: 'Martin Luther',
394
394
  addresses: [
395
395
  { city: 'Frankfurt', country: 'Germany' },
396
396
  { city: 'Christchurch', country: 'New Zealand' }
397
397
  ],
398
398
  address: { country: 'Germany' },
399
- pet: { dog: { _id: inserted._id, name: 'Scruff', user: inserted2._id }},
400
- dogs: [{ _id: inserted._id, name: 'Scruff', user: inserted2._id }]
399
+ pet: { dog: { _id: dog1._id, name: 'Scruff', user: user1._id }},
400
+ dogs: [{ _id: dog1._id, name: 'Scruff', user: user1._id }]
401
401
  })
402
402
 
403
403
  // Blacklisted default field population test
404
- let find2 = await user.findOne({
405
- query: inserted2._id,
404
+ let find2 = await db.user.findOne({
405
+ query: user1._id,
406
406
  populate: ['pet.dog', {
407
407
  from: 'dog',
408
408
  localField: '_id',
@@ -413,24 +413,82 @@ module.exports = function(monastery, opendb) {
413
413
  // ^ great test (address should cancel addresses if not stopping at the .)
414
414
  })
415
415
  expect(find2).toEqual({
416
- _id: inserted2._id,
416
+ _id: user1._id,
417
417
  name: 'Martin Luther',
418
418
  addresses: [{ city: 'Frankfurt' }, { city: 'Christchurch' }],
419
- pet: { dog: { _id: inserted._id, name: 'Scruff', user: inserted2._id }},
420
- dogs: [{ _id: inserted._id, user: inserted2._id }]
419
+ pet: { dog: { _id: dog1._id, name: 'Scruff', user: user1._id }},
420
+ dogs: [{ _id: dog1._id, user: user1._id }]
421
421
  })
422
422
 
423
423
  db.close()
424
424
  })
425
425
 
426
- test('remove basics', async () => {
426
+ test('findOneAndUpdate general', async () => {
427
+ // todo: test all findOneAndUpdate options
428
+ // todo: test find & update hooks
427
429
  let db = (await opendb(null)).db
430
+ let dog = db.model('dog', {
431
+ fields: {
432
+ name: { type: 'string', default: 'Scruff' },
433
+ }
434
+ })
428
435
  let user = db.model('user', {
436
+ fields: {
437
+ name: { type: 'string', default: 'Martin' },
438
+ dog: { model: 'dog' },
439
+ }
440
+ })
441
+
442
+ // Returns omitted field after update, i.e. dog
443
+ let dog1 = await dog.insert({ data: {} })
444
+ let user1 = await user.insert({ data: { dog: dog1._id }})
445
+ expect(await user.findOneAndUpdate({ query: { _id: user1._id }, data: { name: 'Martin2' }})).toEqual({
446
+ _id: user1._id,
447
+ name: 'Martin2',
448
+ dog: dog1._id,
449
+ })
450
+
451
+ // Returns omitted field requiring population after update, i.e. dog
452
+ expect(await user.findOneAndUpdate({
453
+ query: { _id: user1._id },
454
+ data: { name: 'Martin2' },
455
+ populate: ['dog'],
456
+ })).toEqual({
457
+ _id: user1._id,
458
+ name: 'Martin2',
459
+ dog: {
460
+ _id: dog1._id,
461
+ name: 'Scruff'
462
+ },
463
+ })
464
+
465
+ // Error finding document to update
466
+ expect(await user.findOneAndUpdate({
467
+ query: { _id: db.id() },
468
+ data: { name: 'Martin2' },
469
+ })).toEqual(null)
470
+
471
+ // Error finding document to update (populate)
472
+ expect(await user.findOneAndUpdate({
473
+ query: { _id: db.id() },
474
+ data: { name: 'Martin2' },
475
+ populate: ['dog'],
476
+ })).toEqual(null)
477
+
478
+ db.close()
479
+ })
480
+
481
+ test('remove general', async () => {
482
+ let db = (await opendb(null)).db
483
+ let user = db.model('userRemove', {
429
484
  fields: {
430
485
  name: { type: 'string' },
431
486
  },
432
487
  })
433
488
 
489
+ // Clear (incase of failed a test)
490
+ user._remove({}, { multi: true })
491
+
434
492
  // Insert multiple
435
493
  let inserted2 = await user.insert({ data: [{ name: 'Martin' }, { name: 'Martin' }, { name: 'Martin' }]})
436
494
  expect(inserted2).toEqual([
@@ -0,0 +1,122 @@
1
+ module.exports.bird = {
2
+ schema: function() {
3
+ return {
4
+ fields: {
5
+ color: { type: 'string', default: 'red' },
6
+ height: { type: 'number' },
7
+ name: { type: 'string' },
8
+ sub: {
9
+ color: { type: 'string', default: 'red' },
10
+ },
11
+ wing: {
12
+ size: { type: 'number' },
13
+ sizes: {
14
+ one: { type: 'number' },
15
+ two: { type: 'number' },
16
+ }
17
+ },
18
+ },
19
+ findBL: ['wing'],
20
+ }
21
+ },
22
+ mock: function() {
23
+ return {
24
+ height: 12,
25
+ name: 'Ponyo',
26
+ sub: {},
27
+ wing: { size: 1, sizes: { one: 1, two: 1 }},
28
+ }
29
+ },
30
+ }
31
+
32
+ module.exports.user = {
33
+ schema: function() {
34
+ return {
35
+ fields: {
36
+ bird: { model: 'bird' },
37
+ list: [{ type: 'number' }],
38
+ dog: { type: 'string' },
39
+ pet: { type: 'string' },
40
+ pets: [{
41
+ name: { type: 'string'},
42
+ age: { type: 'number'}
43
+ }],
44
+ animals: {
45
+ cat: { type: 'string' },
46
+ dog: { type: 'string' }
47
+ },
48
+ hiddenPets: [{
49
+ name: { type: 'string'}
50
+ }],
51
+ hiddenList: [{ type: 'number'}],
52
+ deep: {
53
+ deep2: {
54
+ deep3: {
55
+ deep4: { type: 'string' }
56
+ }
57
+ }
58
+ },
59
+ deeper: {
60
+ deeper2: {
61
+ deeper3: {
62
+ deeper4: { type: 'string' }
63
+ }
64
+ }
65
+ },
66
+ deepModel: {
67
+ myBird: { model: 'bird' }
68
+ },
69
+ hiddenDeepModel: {
70
+ myBird: { model: 'bird' }
71
+ },
72
+ },
73
+ findBL: [
74
+ 'dog',
75
+ 'animals.cat',
76
+ 'pets.age',
77
+ 'hiddenPets',
78
+ 'hiddenList',
79
+ 'deep.deep2.deep3',
80
+ 'deeper',
81
+ 'hiddenDeepModel',
82
+ ],
83
+ }
84
+ },
85
+ mock: function(bird1) {
86
+ return {
87
+ bird: bird1._id,
88
+ list: [44, 54],
89
+ dog: 'Bruce',
90
+ pet: 'Freddy',
91
+ pets: [{ name: 'Pluto', age: 5 }, { name: 'Milo', age: 4 }],
92
+ animals: {
93
+ cat: 'Ginger',
94
+ dog: 'Max'
95
+ },
96
+ hiddenPets: [{
97
+ name: 'secretPet'
98
+ }],
99
+ hiddenList: [12, 23],
100
+ deep: {
101
+ deep2: {
102
+ deep3: {
103
+ deep4: 'hideme'
104
+ }
105
+ }
106
+ },
107
+ deeper: {
108
+ deeper2: {
109
+ deeper3: {
110
+ deeper4: 'hideme'
111
+ }
112
+ }
113
+ },
114
+ deepModel: {
115
+ myBird: bird1._id
116
+ },
117
+ hiddenDeepModel: {
118
+ myBird: bird1._id
119
+ }
120
+ }
121
+ },
122
+ }
package/test/monk.js CHANGED
@@ -1,6 +1,6 @@
1
1
  module.exports = function(monastery, opendb) {
2
2
 
3
- test('Monk confilicts', async () => {
3
+ test('Monk conflicts', async () => {
4
4
  // Setup
5
5
  let db = (await opendb(false)).db
6
6
  let monkdb = require('monk')(':badconnection', () => {})
package/test/test.js CHANGED
@@ -12,6 +12,8 @@
12
12
  * - expect.objectContaining:
13
13
  */
14
14
 
15
+ global.oid = require('mongodb').ObjectID
16
+
15
17
  let monastery = require('../lib')
16
18
  let opendb = async function(uri, opts) {
17
19
  let db = monastery(
package/test/validate.js CHANGED
@@ -808,8 +808,10 @@ module.exports = function(monastery, opendb) {
808
808
  })
809
809
  })
810
810
 
811
- test('schema default rules', async () => {
811
+ test('schema options default', async () => {
812
812
  // Setup
813
+ // todo: test objects
814
+ // todo: change test name
813
815
  let db = (await opendb(false)).db
814
816
  let user = db.model('user', { fields: {
815
817
  name: { type: 'string', minLength: 7 },
@@ -889,7 +891,35 @@ module.exports = function(monastery, opendb) {
889
891
  await expect(user.validate({ amount: 'bad' })).rejects.toContainEqual(mock2)
890
892
  })
891
893
 
892
- test('schema default objects', async () => {
894
+ test('schema options objects', async () => {
895
+ let db = (await opendb(null, {
896
+ timestamps: false,
897
+ nullObjects: true,
898
+ serverSelectionTimeoutMS: 2000
899
+ })).db
900
+ let user = db.model('user', {
901
+ fields: {
902
+ location: {
903
+ lat: { type: 'number' },
904
+ lng: { type: 'number' },
905
+ schema: { required: true },
906
+ },
907
+ },
908
+ })
909
+
910
+ // Subdocument data (null/string)
911
+ await expect(user.validate({ location: null })).rejects.toEqual([{
912
+ 'detail': 'This field is required.',
913
+ 'meta': {'detailLong': undefined, 'field': 'location', 'model': 'user', 'rule': 'required'},
914
+ 'status': '400',
915
+ 'title': 'location'
916
+ }])
917
+ await expect(user.validate({ location: {} })).resolves.toEqual({ location: {} })
918
+
919
+ db.close()
920
+ })
921
+
922
+ test('validate defaultObjects', async () => {
893
923
  let db = (await opendb(null, {
894
924
  timestamps: false,
895
925
  defaultObjects: true,
@@ -915,7 +945,7 @@ module.exports = function(monastery, opendb) {
915
945
  db.close()
916
946
  })
917
947
 
918
- test('schema nullObjects', async () => {
948
+ test('validate nullObjects', async () => {
919
949
  let db = (await opendb(null, {
920
950
  timestamps: false,
921
951
  nullObjects: true,
@@ -1,313 +0,0 @@
1
- ---
2
- title: Schema
3
- nav_order: 4
4
- has_children: true
5
- ---
6
-
7
- # Schema
8
-
9
- Model schema object.
10
-
11
- ### Table of Contents
12
-
13
- - [Fields](#fields)
14
- - [Field blacklisting](#field-blacklisting)
15
- - [Field options](#field-options)
16
- - [MongoDB indexes](#mongodb-indexes)
17
- - [Custom validation rules](#custom-validation-rules)
18
- - [Custom error messages](#custom-error-messages)
19
- - [Operation hooks](#operation-hooks)
20
- - [Full example](#full-example)
21
-
22
- ### Fields
23
-
24
- 1. Fields may contain subdocuments, array of values, or an array of subdocuments
25
- 2. Field values need to have the `type` rule defined
26
- 3. Field values can contain [custom](#custom-validation-rules) and [default validation rules](./rules), e.g. `{ minLength: 2 }`
27
- 4. Field values can contain [field options](#field-options).
28
-
29
- ```js
30
- schema.fields = {
31
- name: { // value
32
- type: 'string',
33
- required: true
34
- },
35
- address: { // subdocument
36
- line1: { type: 'string', required: true },
37
- city: { type: 'string', minLength: 2 }
38
- },
39
- names: [ // array of values
40
- { type: 'string' }
41
- ],
42
- pets: [{ // array of subdocuments
43
- name: { type: 'string' },
44
- type: { type: 'string' }
45
- }]
46
- }
47
- ```
48
-
49
- These fields automatically get assigned and take presidence over any input data when [`manager.timestamps`](./manager) is true (default). You can override the `timestamps` value per operation, e.g. `db.user.update({ ..., timestamps: false})`. These fields use unix timestamps in seconds (by default), but can be configured to use use milliseconds via the manager [`useMilliseconds` ](./manager) option.
50
-
51
- ```js
52
- schema.fields = {
53
- createdAt: {
54
- type: 'date',
55
- insertOnly: true,
56
- default: function() { return Math.floor(Date.now() / 1000) }
57
- },
58
- updatedAt: {
59
- type: 'date',
60
- default: function() { return Math.floor(Date.now() / 1000) }
61
- }
62
- }
63
- ```
64
-
65
- ### Field blacklisting
66
-
67
- You are able to provide a list of fields to blacklist per model operation.
68
-
69
- ```js
70
- // The 'password' field will be removed from the results returned from `model.find`
71
- schema.findBL = ['password']
72
-
73
- // The 'password' field will be removed before inserting via `model.insert`
74
- schema.insertBL = ['password']
75
-
76
- // The 'password' and 'createdAt' fields will be removed before updating via `model.update`
77
- schema.updateBL = ['createdAt', 'password']
78
- ```
79
-
80
- You are also able to blacklist nested fields within subdocuments and arrays of subdocuments.
81
-
82
- ```js
83
- // Subdocument example: `address.city` will be excluded from the response
84
- schema.findBL = ['address.city']
85
-
86
- // Array of subdocuments example: `meta` will be removed from each comment in the array
87
- schema.findBL = ['comments.meta']
88
- ```
89
-
90
- ### Field options
91
-
92
- Here are some other special field options that can be used alongside validation rules.
93
-
94
- ```js
95
- let fieldName = {
96
-
97
- // Enables population, you would save the foreign document _id on this field.
98
- model: 'pet',
99
-
100
- // Field will only be allowed to be set on insert when calling model.insert
101
- insertOnly: true,
102
-
103
- // Default will always override any passed value (it has some use-cases)
104
- defaultOverride: true,
105
-
106
- // Default value
107
- default: 12,
108
-
109
- // Default value can be returned from a function. `this` refers to the data object, but be
110
- // sure to pass any referenced default fields along with insert/update/validate, e.g. `this.age`
111
- default: function(fieldName, model) { return `I'm ${this.age} years old` },
112
-
113
- // Monastery will automatically create a mongodb index for this field, see "MongoDB indexes"
114
- // below for more information
115
- index: true|1|-1|'text'|'unique'|Object,
116
-
117
- // The field won't stored, handy for fields that get populated with documents, see ./find for more details
118
- virtual: true
119
- }
120
- ```
121
-
122
- ### MongoDB indexes
123
-
124
- You are able to automatically setup MongoDB indexes via the `index` field option.
125
-
126
- ```js
127
- let fieldName = {
128
- // This will create an ascending / descending index for this field
129
- index: true|1|-1,
130
-
131
- // This will create an ascending unique index which translates:
132
- // { key: { [fieldName]: 1 }, unique: true }
133
- index: 'unique',
134
-
135
- // Text indexes are handled a little differently in which all the fields on the model
136
- // schema that have a `index: 'text` set are collated into one index, e.g.
137
- // { key: { [fieldName1]: 'text', [fieldName2]: 'text', .. }}
138
- index: 'text'
139
-
140
- // You can also pass an object if you need to use mongodb's index options
141
- // https://docs.mongodb.com/manual/reference/command/createIndexes/
142
- // https://mongodb.github.io/node-mongodb-native/2.1/api/Collection.html#createIndexes
143
- index: { type: 1, ...(any mongodb index option) },
144
- }
145
- ```
146
-
147
- And here's how you would use a 2dsphere index, e.g.
148
-
149
- ```js
150
- schema.fields = {
151
- name: {
152
- type: 'string',
153
- required: true
154
- },
155
- location: {
156
- index: '2dsphere',
157
- type: { type: 'string', default: 'Point' },
158
- coordinates: [{ type: 'number' }] // lng, lat
159
- }
160
- }
161
-
162
- // Inserting a 2dsphere point
163
- await db.user.insert({
164
- data: {
165
- location: { coordinates: [170.2628528648167, -43.59467883784971] }
166
- }
167
- }
168
- ```
169
-
170
- Since unique indexes by default don't allow mutliple documents with `null`, you use a partial index (less performant), e.g.
171
-
172
- ```js
173
-
174
- schema.fields = {
175
- index: {
176
- name: {
177
- type: 'string',
178
- index: {
179
- type: 'unique',
180
- partialFilterExpression: {
181
- email: { $type: 'string' }
182
- }
183
- }
184
- }
185
- }
186
- ```
187
-
188
- ### Custom validation rules
189
-
190
- You are able to define custom validation rules to use. (`this` will refer to the data object passed in)
191
-
192
- ```js
193
- schema.rules = {
194
- // Basic definition
195
- isGrandMaster: function(value, ruleArgument, path, model) {
196
- return (value == 'Martin Luther')? true : false
197
- },
198
- // Full definition
199
- isGrandMaster: {
200
- message: (value, ruleArgument, path, model) => 'Only grand masters are permitted'
201
- fn: function(value, ruleArgument, path, model) {
202
- return (value == 'Martin Luther' || this.age > 100)? true : false
203
- }
204
- }
205
- }
206
-
207
- // And referencing is the same as any other builtin rule
208
- schema.fields = {
209
- user: {
210
- name: {
211
- type: 'string'
212
- isGrandMaster: true // true is the ruleArgument
213
- }
214
- }
215
- }
216
-
217
- // Additionally, you can define custom messages here
218
- schema.messages = {
219
- 'user.name': {
220
- isGrandMaster: 'Only grand masters are permitted'
221
- }
222
- }
223
- ```
224
-
225
- ### Custom error messages
226
-
227
- You are able to define custom error messages for each validation rule.
228
-
229
- ```js
230
- schema.messages = {
231
- 'name': {
232
- required: 'Sorry, even a monk cannot be nameless'
233
- type: 'Sorry, your name needs to be a string'
234
- },
235
- 'address.city': {
236
- minLength: (value, ruleArgument, path, model) => {
237
- return `Is your city of residence really only ${ruleArgument} characters long?`
238
- }
239
- },
240
- // You can assign custom error messages for all subdocument fields in an array
241
- // e.g. pets = [{ name: { type: 'string' }}]
242
- 'pets.name': {
243
- required: `Your pet's name needs to be a string.`
244
- }
245
- // To target a specific array item
246
- 'pets.0.name': {
247
- required: `You first pet needs a name`
248
- }
249
- // You can also target any rules set on the array or sub arrays
250
- // e.g.
251
- // // let arrayWithSchema = (array, schema) => { array.schema = schema; return array }, OR you can use db.arrayWithSchema
252
- // petGroups = db.arrayWithSchema(
253
- // [db.arrayWithSchema(
254
- // [{ name: { type: 'string' }}],
255
- // { minLength: 1 }
256
- // )],
257
- // { minLength: 1 }
258
- // )
259
- 'petGroups': {
260
- minLength: `Please add at least one pet pet group.`
261
- }
262
- 'petGroups.$': {
263
- minLength: `Please add at least one pet into your pet group.`
264
- }
265
- }
266
- ```
267
-
268
- ### Operation hooks
269
-
270
- You are able provide an array of callbacks to these model operation hooks. If you need to throw an error asynchronously, please pass an error as the first argument to `next()`, e.g. `next(new Error('Your error here'))`. You can also access the operation details via `this` in each callback.
271
-
272
- ```js
273
- schema.afterFind = [function(data, next) {}]
274
- schema.afterInsert = [function(data, next) {}]
275
- schema.afterInsertUpdate = [function(data, next) {}]
276
- schema.afterUpdate = [function(data, next) {}]
277
- schema.afterRemove = [function(next) {}]
278
- schema.beforeInsert = [function(data, next) {}]
279
- schema.beforeInsertUpdate = [function(data, next) {}]
280
- schema.beforeUpdate = [function(data, next) {}]
281
- schema.beforeRemove = [function(next) {}]
282
- schema.beforeValidate = [function(data, next) {}]
283
- ```
284
-
285
- ### Full example
286
-
287
- ```js
288
- let schema = {
289
- fields: {
290
- email: { type: 'email', required: true, index: 'unique' },
291
- firstName: { type: 'string', required: true },
292
- lastName: { type: 'string' }
293
- },
294
-
295
- messages: {
296
- email: { required: 'Please enter an email.' }
297
- },
298
-
299
- updateBL: ['email'],
300
-
301
- beforeValidate: [function (data, next) {
302
- if (data.firstName) data.firstName = util.ucFirst(data.firstName)
303
- if (data.lastName) data.lastName = util.ucFirst(data.lastName)
304
- next()
305
- }],
306
-
307
- afterFind: [function(data) {// Synchronous
308
- data = data || {}
309
- data.name = data.firstName + ' ' + data.lastName
310
- }]
311
- }
312
-
313
- ```